博客中代码地址:https://github.com/farliu/farpc.git
这章继续了解SPI,上一章我们列举了dubbo选择SPI的背景和SPI的简单使用。不过,dubbo并未使用 Java 原生的SPI机制,而是对其进行了增强,使其能够更好的满足需求。我列举两点dubbo增强的优势。本章也对其进行展开。
按需加载接口实现类
增加了IOC和AOP等特性,向拓展对象中注入依赖
dubbo SPI示例 dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。以下例子,取自dubbo的单测(dubbo-common模块)。
org.apache.dubbo.common.extension.ext1.SimpleExt
1 2 3 4 impl1 =org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl1impl2 =org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl2 impl3 =org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl3
ExtensionLoaderTest
1 2 3 4 5 @Test public void test_getExtension() throws Exception { assert True(ExtensionLoader.getExtensionLoader (SimpleExt.class ) .getExtension("impl1" ) instanceof SimpleExtImpl1); assert True(ExtensionLoader.getExtensionLoader (SimpleExt.class ) .getExtension("impl2" ) instanceof SimpleExtImpl2); }
结果单测肯定是通过的。也就是说我们通过impl1和impl2分别获取到了各自的实现类的对象。
总揽全局原理 我们粗略归纳一下,以上单测的运行逻辑,主要包含两个方法getExtensionLoader()和getExtension(),前者用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例,并创建一个objectFactory用于实现IOC注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public static <T > ExtensionLoader <T > getExtensionLoader(Class <T > type ) { if (type == null ) { throw new IllegalArgumentException ("Extension type == null" ); } if (!type .isInterface ( )) { throw new IllegalArgumentException ("Extension type (" + type + ") is not an interface! ") ; } if (!withExtensionAnnotation(type )) { throw new IllegalArgumentException ("Extension type (" + type + ") is not an extension , because it is NOT annotated with @ " + SPI .class .getSimpleName ( ) + "! ") ; } ExtensionLoader <T > loader = (ExtensionLoader <T >) EXTENSION_LOADERS .get(type ) ; if (loader == null ) { EXTENSION_LOADERS .putIfAbsent(type , new ExtensionLoader<T> (type )) ; loader = (ExtensionLoader <T >) EXTENSION_LOADERS .get(type ) ; } return loader; } private final ExtensionFactory objectFactory;private ExtensionLoader (Class <?> type ) { this .type = type ; objectFactory = (type == ExtensionFactory .class ? null : ExtensionLoader .getExtensionLoader(ExtensionFactory .class ).getAdaptiveExtension ( )) ; }
getExtension()代码的逻辑比较简单,首先检查缓存,缓存未命中则调用createExtension()创建拓展对象。值得一提的是Holder,该类使用volatile修饰,使用Holder包装保证可见性。看源码更重要的时候学到这些细节,dubbo中对细节处理很到位,很多地方用到了双重检查和缓存等优化,这些平常到不能再平常的处理,让我对dubbo源码心生敬畏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public T getExtension(String name) { //一系列安全检查 if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null" ); } if ("true" .equals(name)) { // 获取默认的拓展实现类 return getDefaultExtension(); } //从缓存中获取,没有则new一个并保存 Holder<Object> holder = getOrCreateHolder(name); Object instance = holder.get(); // 双重检查 if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { // 创建拓展实例 instance = createExtension(name); holder.set(instance); } } } return (T) instance; }
createExtension()是SPI一览全景的方法,这个方法包含了创建对象的所有精髓。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private T createExtension(String name ) { // 根据传入的扩展名获取到对应实现类 Class <?> clazz = getExtensionClasses().get (name ); if (clazz == null ) { throw findException(name ); } try { T instance = (T) EXTENSION_INSTANCES.get (clazz); if (instance == null ) { // 通过反射创建实例 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get (clazz); } // 向实例中注入依赖 injectExtension(instance); Set <Class <?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { // 循环创建 Wrapper 实例 for (Class <?> wrapperClass : wrapperClasses) { // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。 // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量 instance = injectExtension( (T) wrapperClass.getConstructor(type ).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("..."); } }
createExtension()方法主要包含了如下的步骤:
根据传入的扩展名获取到对应实现类
通过反射创建拓展对象
向拓展对象中注入依赖
将拓展对象包裹在相应的Wrapper对象中
第一步是加载拓展类的关键,第二步是SPI的核心,第三和第四个步骤是dubbo IOC与AOP的具体实现。接下来主要细品这些内容。
细品:按需加载接口实现类 getExtensionClasses()该方法配置文件中加载所有的拓展类,返回Map<String, Class<?>>用来保存“配置项名称”到“配置类”的关系。代码逻辑简单,就是常规的使用DCL检查缓存,最后通过 loadExtensionClasses()加载拓展类。我们以loadExtensionClasses()为入口进行分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private Map<String, Class<?>> loadExtensionClasses() { cacheDefaultExtensionName() ; Map<String, Class<?>> extensionClasses = new HashMap<>() ; loadDirectory(extensionClasses , DUBBO_INTERNAL_DIRECTORY, type .getName () ); loadDirectory(extensionClasses , DUBBO_INTERNAL_DIRECTORY, type .getName () .replace("org.apache" , "com.alibaba" )); loadDirectory(extensionClasses , DUBBO_DIRECTORY, type .getName () ); loadDirectory(extensionClasses , DUBBO_DIRECTORY, type .getName () .replace("org.apache" , "com.alibaba" )); loadDirectory(extensionClasses , SERVICES_DIRECTORY, type .getName () ); loadDirectory(extensionClasses , SERVICES_DIRECTORY, type .getName () .replace("org.apache" , "com.alibaba" )); return extensionClasses; }
loadExtensionClasses()一共做两件事,一是从接口上的SPI注解里提取并缓存默认扩展名,这段代码逻辑简单。二是调用loadDirectory()加载制定目录下所有的配置文件,我们重点关注第二点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void loadDirectory(Map<String, Class<?>> extensionClasses , String dir ) { String fileName = dir + type .getName() ; try { Enumeration<java.net.URL> urls; ClassLoader classLoader = findClassLoader() ; if (classLoader != null) { urls = classLoader.getResources(fileName ) ; } else { urls = ClassLoader . getSystemResources(fileName ) ; } if (urls != null) { while (urls.hasMoreElements() ) { java.net.URL resourceURL = urls.nextElement() ; loadResource(extensionClasses , classLoader , resourceURL ) ; } } } catch (Throwable t) { logger.error("..." ); } }
loadDirectory()主要是通过classLoader加载和fileName同名的所有资源文件,这里的资源也就是配置文件,然后调用loadResource()解析配置文件。其实findClassLoader()也建议大家好好端详一番,感受一下大佬的细致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 private void loadResource(Map<String , Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { try { BufferedReader reader = new BufferedReader ( new InputStreamReader(resourceURL.openStream(), "utf-8" )); try { String line ; while ((line = reader.readLine()) != null ) { final int ci = line .indexOf('#' ); if (ci >= 0 ) { line = line .substring(0 , ci); } line = line .trim (); if (line .length() > 0 ) { try { String name = null ; int i = line .indexOf('=' ); if (i > 0 ) { name = line .substring(0 , i).trim (); line = line .substring(i + 1 ).trim (); } if (line .length() > 0 ) { loadClass(extensionClasses, resourceURL, Class.forName(line , true , classLoader), name); } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class..." ); } } } } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class..." ); } }
loadResource()就是读取文件内容并解析,通过Class.forName()加载类,然后调用loadClass操作缓存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 private void loadClass(Map<String, Class <?>> extensionClasses, java.net.URL resourceURL, Class <?> clazz, String name ) throws NoSuchMethodException { if (!type .isAssignableFrom(clazz)) { throw new IllegalStateException("Error occurred when loading extension class (interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + " is not subtype of interface."); } // 检测类是否标注Adaptive注解,使用一个变量保存起来 if (clazz.isAnnotationPresent(Adaptive.class )) { cacheAdaptiveClass(clazz); } // 检测class 是否是Wapper类型,使用一个变量保存起来 else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else { clazz.getConstructor(); if (StringUtils.isEmpty(name )) { // name 为空,则尝试从 Extension 注解中获取 name name = findAnnotationName(clazz); if (name .length() == 0 ) { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name ); if (ArrayUtils.isNotEmpty(names)) { // 如果存在Activate注解,存储name 到Activate注解对象的映射关系 cacheActivateClass(clazz, names[0 ]); for (String n : names) { // 保存Class 到名称的关系 cacheName(clazz, n); // 保存名称到Class 的关系到extensionClasses,以此返回 saveInExtensionClass(extensionClasses, clazz, name ); } } } }
至此,第一个特性按需加载接口实现类,所有实现都结束了,这个过程逻辑并不复杂,顺着这个思路或跟着断点走一遍基本上心里都有个七七八八。createExtension()的第二步通过反射获取对象,这里没有过多追述的。
细品:IOC 和 AOP 特性 SPI中的IOC dubbo IOC是通过判断是否存在set方法,通过前文说的ObjectFactory对象获取注入对象。然后将注入对象通过反射调用set方法赋值到目标对象中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private T injectExtension(T instance ) { try { if (objectFactory != null) { for (Method method : instance.getClass() .getMethods() ) { if (isSetter(method ) ) { if (method .getAnnotation(DisableInject.class ) != null) { continue; } Class<?> pt = method .getParameterTypes() [0 ] ; if (ReflectUtils . isPrimitives(pt ) ) { continue; } try { String property = getSetterProperty(method ) ; Object object = objectFactory.getExtension(pt , property ) ; if (object != null) { method .invoke(instance, object); } } catch (Exception e) { logger.error("Failed to inject via method " + method .getName() + " of interface " + type .getName() + ": " + e.getMessage() , e); } } } } } catch (Exception e) { logger.error(e.getMessage() , e); } return instance; }
讲起来,这就是IOC的所有的代码了,还是比较容易理解。这里可以继续深入的就是objectFactory.getExtension(),ExtensionFactory每个实现类的getExtension()代码都容易理解。只是多提一下,ExtensionFactory三个实现类的作用。SpiExtensionFactory用于创建自适应的拓展、SpringExtensionFactory用于从Spring的IOC容器中获取所需的拓展、AdaptiveExtensionFactory内部为一个List,遍历所有的getExtension()。
SPI中的AOP dubbo对于SPI所增强的AOP,根本原理是在目标对象上包了一层Wrapper类,Wrapper也实现了目标接口,通过Wrapper的构造将目标对象保存至Wrapper对象中,而ExtensionLoader 返回扩展对象时,返回的Wrapper类的对象。我们将扩展对象的公共逻辑移至Wrapper类中,达到AOP的效果。Wrapper可以根据需要新增,切面的执行顺序按照配置文件中顺序决定。
了解了基础原理,我们先看一个简单的demo,该代码取自dubbo-common中的单元测试。
org.apache.dubbo.common.extension.ext6_wrap.WrappedExt
1 2 3 4 impl1=org.apache .dubbo .common .extension .ext6_wrap .impl .Ext5Impl1 impl2=org.apache .dubbo .common .extension .ext6_wrap .impl .Ext5Impl2 wrapper1=org.apache .dubbo .common .extension .ext6_wrap .impl .Ext5Wrapper1 wrapper2=org.apache .dubbo .common .extension .ext6_wrap .impl .Ext5Wrapper2
ExtensionLoaderTest
1 2 3 4 5 6 7 8 @Test public void test_getExtension_WithWrapper() throws Exception { WrappedExt impl1 = ExtensionLoader . getExtensionLoader(WrappedExt.class ) .getExtension("impl1" ) ; assert That(impl1 , anyOf (instanceOf (Ext5Wrapper1.class ) , instanceOf(Ext5Wrapper2.class ) )); WrappedExt impl2 = ExtensionLoader . getExtensionLoader(WrappedExt.class ) .getExtension("impl2" ) ; assert That(impl2 , anyOf (instanceOf (Ext5Wrapper1.class ) , instanceOf(Ext5Wrapper2.class ) )); }
我们打断点,先证实我上面所说的原理,观察impl1所返回的对象来自哪个类。
可以看到ExtensionLoader返回的扩展对象并非Ext5Impl1,而是Ext5Wrapper1,而Ext5Wrapper1中存在一个instance变量,该变量存的对象是Ext5Impl2,Ext5Impl2中的instance存的才是Ext5Impl1的对象。也就是说当我们调用WrappedExt接口中的方法时,会依次经过Ext5Wrapper1 > Ext5Wrapper2 > Ext5Impl1。
这也印证了之前说的执行顺序,执行顺序除了受配置文件中的顺序决定以外,还有一点值得注意。在上文分析的loadExtensionClasses()方法中有loadDirectory()一说,Wrapper的执行还跟加载文件夹有关。也就是:internal > META-INF/dubbo/ > META-INF/services/
基本基础知识了解了,再来看看怎么实现的,其实在上文中,都隐约有提到对于Wrapper的东西。这里再次将关键代码提取出来,应该更能理解。
createExtension() > getExtensionClasses() > loadExtensionClasses() > loadDirectory() > loadResource() > loadClass()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private void loadClass(Map<String, Class<?>> extensionClasses , java .net .URL resourceURL , Class<?> clazz , String name ) throws NoSuchMethodException { ... if (clazz.isAnnotationPresent(Adaptive.class ) ) { cacheAdaptiveClass(clazz ) ; } else if (isWrapperClass(clazz ) ) { if (cachedWrapperClasses == null) { cachedWrapperClasses = new ConcurrentHashSet<>() ; } cachedWrapperClasses.add(clazz); } else { ... } } private boolean isWrapperClass(Class<?> clazz ) { try { clazz.getConstructor(type ) ; return true ; } catch (NoSuchMethodException e) { return false ; } }
createExtension()
1 2 3 4 5 6 7 8 9 10 11 12 13 private T createExtension(String name ) {... Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils . isNotEmpty(wrapperClasses ) ) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type ) .new Instance(instance ) ); } } return instance; ... }
至此,dubbo SPI的特性全部解析清楚,剩下一个扩展点自适应机制,该机制逻辑晦涩难懂,这里讲诉篇幅过长,后续再做安排。本章有点啰嗦,后续可能不会这么详细,尽快的覆盖到整个dubbo,再做细致讲解。